package com.phospherus.common.service.impl;

import com.phospherus.common.repository.BaseRepository;
import com.phospherus.common.service.IBaseService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.Path;
import javax.persistence.criteria.Predicate;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @author songjinlong
 * @version 1.0
 * @since 2017/11/8
 */
public class BaseServiceImpl<T, ID extends Serializable, REPOSITORY extends BaseRepository<T, ID>> implements IBaseService<T, ID, REPOSITORY> {
    @Autowired
    protected REPOSITORY repository;

    protected Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public <S extends T> S save(S entity) {
        return repository.save(entity);
    }

    @Override
    public <S extends T> Iterable<S> save(Iterable<S> entities) {
        return repository.save(entities);
    }

    @Override
    public T findOne(ID id) {
        return repository.getOne(id);
    }

    @Override
    public boolean exists(ID id) {
        return repository.exists(id);
    }

    @Override
    public List<T> findAll() {
        return repository.findAll();
    }

    @Override
    public List<T> findAll(Iterable<ID> ids) {
        return repository.findAll(ids);
    }

    @Override
    public Page<T> findAll(int currPage, int pageSize) {
        Pageable pageable = new PageRequest(currPage, pageSize, createSort(null));
        return repository.findAll(pageable);
    }

    /**
     * 如果需要排序，子类重写该方法，用于findAll(Map<String,String>) 方法排序
     *
     * @param param
     * @return
     */
    protected Sort createSort(Map<String, String> param) {
        String orders = param.get("ORDER");
        if (orders == null) {
            return null;
        }
        param.remove("ORDER");
        String[] orderArray = orders.split(",");
        List<Sort.Order> orderList = Arrays.stream(orderArray).map(
                it -> {
                    String prefix = getPrefix(it);
                    String postfix = getPostfix(it);
                    Sort.Order order = new Sort.Order(Sort.Direction.valueOf(prefix), postfix);
                    return order;
                }
        ).collect(Collectors.toList());
        Sort sort = new Sort(orderList);
        return sort;
    }

    @Override
    public Page<T> findAll(Map<String, String> param, int currPage, int pageSize) {
        Sort sort = createSort(param);
        Specification specification = createSpecification(param);
        PageRequest pageRequest = new PageRequest(currPage, pageSize, sort);
        return repository.findAll(specification, pageRequest);
    }

    @Override
    public List<T> findAll(Map<String, String> param) {
        Sort sort = createSort(param);
        Specification specification = createSpecification(param);
        return sort == null ? repository.findAll(specification) : repository.findAll(specification, sort);
    }


    @Override
    public long count() {
        return repository.count();
    }

    @Override
    public void delete(ID id) {
        repository.delete(id);
    }

    /**
     * 动态创建查询条件
     *
     * @param param
     * @return
     */
    private Specification createSpecification(final Map<String, String> param) {
        return param == null ? null : (Specification<T>) (root, criteriaQuery, criteriaBuilder) -> {
            Predicate predicate = criteriaBuilder.conjunction();

            for (Map.Entry<String, String> entry : param.entrySet()) {
                String prefix = getPrefix(entry.getKey());
                String postfix = getPostfix(entry.getKey());
                String value = entry.getValue();
                if (StringUtils.isBlank(value)) {
                    continue;
                }
                Path path = root.get(postfix);
                Class pathType = path.getJavaType();

                switch (prefix) {
                    default:
                    case "eq":
                        if (pathType == Date.class) {
                            predicate.getExpressions().add(criteriaBuilder.equal(path, new Date(Long.parseLong(value))));
                        } else {
                            predicate.getExpressions().add(criteriaBuilder.equal(path, value));
                        }
                        break;
                    case "lt":
                        if (pathType == Date.class) {
                            predicate.getExpressions().add(criteriaBuilder.lessThan(path, new Date(Long.parseLong(value))));
                        } else {
                            predicate.getExpressions().add(criteriaBuilder.lessThan(path, value));
                        }
                        break;
                    case "elt":
                        if (pathType == Date.class) {
                            predicate.getExpressions().add(criteriaBuilder.lessThanOrEqualTo(path, new Date(Long.parseLong(value))));
                        } else {
                            predicate.getExpressions().add(criteriaBuilder.lessThanOrEqualTo(path, value));
                        }
                        break;
                    case "gt":
                        if (pathType == Date.class) {
                            predicate.getExpressions().add(criteriaBuilder.greaterThan(path, new Date(Long.parseLong(value))));
                        } else {
                            predicate.getExpressions().add(criteriaBuilder.greaterThan(path, value));
                        }
                        break;
                    case "egt":
                        if (pathType == Date.class) {
                            predicate.getExpressions().add(criteriaBuilder.greaterThanOrEqualTo(path, new Date(Long.parseLong(value))));
                        } else {
                            predicate.getExpressions().add(criteriaBuilder.greaterThanOrEqualTo(path, value));
                        }
                        break;
                    case "like":
                        predicate.getExpressions().add(criteriaBuilder.like(path, "%" + value + "%"));
                        break;
                    case "llike":
                        predicate.getExpressions().add(criteriaBuilder.like(path, "%" + value));
                        break;
                    case "rlike":
                        predicate.getExpressions().add(criteriaBuilder.like(path, value + "%"));
                        break;
                    case "neq":
                        predicate.getExpressions().add(criteriaBuilder.notEqual(path, "%" + value + "%"));
                        break;
                    case "isNull":
                        predicate.getExpressions().add(criteriaBuilder.isNull(path));
                        break;
                    case "isNotNull":
                        predicate.getExpressions().add(criteriaBuilder.isNotNull(path));
                        break;
                }
            }
            return predicate;
        };
    }

    private String getPrefix(String key) {
        return key.indexOf(95) >= 0 ? key.substring(0, key.indexOf(95)) : "";
    }

    private String getPostfix(String key) {
        return key.indexOf(95) >= 0 ? key.substring(key.indexOf(95) + 1) : key;
    }
}